package com.otaliastudios.cameraview.video

import android.graphics.Bitmap
import android.media.MediaCodec
import android.media.MediaCodecInfo
import android.media.MediaFormat
import android.media.MediaMuxer
import android.os.Build
import android.os.SystemClock
import android.util.Log
import android.view.PixelCopy
import com.otaliastudios.cameraview.LogPlus
import com.otaliastudios.cameraview.livedata.RecordPauseLiveData
import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers.IO
import org.opencv.android.Utils
import org.opencv.core.Mat
import org.opencv.core.Size
import org.opencv.videoio.VideoWriter
import java.io.File
import java.io.IOException
import java.nio.ByteBuffer

class VideoRecordEngine(
    private val out: File,
    private val frameRate: Int = 25,
    var srcBitmap: Bitmap
) {
    private lateinit var mediaCodec: MediaCodec
    private var mediaMuxer: MediaMuxer? = null
    private var mMuxerStarted = false
    private var mTrackIndex = 0
    val pause = RecordPauseLiveData.get()
    private var colorFormat = MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar
    private val defaultTimeOutUs = 10000L
    private var rocrodJob: Job? = null
    val widthFix = if (srcBitmap.width % 2 != 0) {
        srcBitmap.width - 1
    } else srcBitmap.width
    val heightFix = if (srcBitmap.height % 2 != 0) {
        srcBitmap.height - 1
    } else srcBitmap.height
    private val videoWriter by lazy {
        VideoWriter(
            out.absolutePath,
            VideoWriter.fourcc('M', 'J', 'P', 'G'),
            frameRate.toDouble(),
            Size(srcBitmap.width.toDouble(), srcBitmap.height.toDouble())
        )
    }
    private val mat by lazy { Mat() }

    fun start(): Boolean {
        if (out.absolutePath.endsWith(".avi", true)) {
            aviRecord()
            return true
        } else {
            if (rocrodJob?.isActive == true) return false
            val mediaFormat =
                MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, widthFix, heightFix)
            mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat)
            mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, widthFix * heightFix * 2)
            mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate)
            mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1)
            try {
                mediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC)
                // Create the generated MP4 initialization object
                out.parentFile.mkdirs()
                if (!out.exists()) {
                    out.createNewFile()
                }
                mediaMuxer =
                    MediaMuxer(out.absolutePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
            } catch (e: IOException) {
                e.printStackTrace()
            }
            mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
            mediaCodec.start()
            run()
            return true
        }
    }


    private fun run() {
        rocrodJob = GlobalScope.launch(IO) {
            val info = MediaCodec.BufferInfo()
            var buffers: Array<ByteBuffer?>? = null
            if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP) {
                buffers = mediaCodec!!.inputBuffers
            }
            while (isActive) {
                val inputBufferIndex = mediaCodec!!.dequeueInputBuffer(defaultTimeOutUs)
                if (inputBufferIndex >= 0) {
                    val ptsUsec = SystemClock.elapsedRealtimeNanos() / 1000
                    val start = SystemClock.elapsedRealtime()
                    val input = getNV12(widthFix, heightFix, srcBitmap)
                    // Valid empty cache
                    val inputBuffer =
                        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP) {
                            buffers!![inputBufferIndex]
                        } else {
                            mediaCodec!!.getInputBuffer(inputBufferIndex)
                        }
                    inputBuffer!!.clear()
                    inputBuffer.put(input)
                    // Put the data on the encoding queue
                    mediaCodec.queueInputBuffer(inputBufferIndex, 0, input.size, ptsUsec, 0)
                    drainEncoder(false, info)
                    //srcBitmap.recycle() //释放bitmap
                    LogPlus.i("bitmapToNV12===>${SystemClock.elapsedRealtime() - start}")

                } else {
                    delay(50)
                }
            }
            val inputBufferIndex = mediaCodec.dequeueInputBuffer(defaultTimeOutUs)
            if (inputBufferIndex >= 0) {
                val ptsUsec = SystemClock.elapsedRealtimeNanos() / 1000
                mediaCodec.queueInputBuffer(
                    inputBufferIndex,
                    0,
                    0,
                    ptsUsec,
                    MediaCodec.BUFFER_FLAG_END_OF_STREAM
                )
                drainEncoder(true, info)
            }
            mediaCodec.stop()
            mediaCodec.release()
            mediaMuxer?.let {
                if (mMuxerStarted) {
                    it.stop()
                    it.release()
                }
            }
        }
    }


   private fun aviRecord() {
        rocrodJob = GlobalScope.launch(IO) {
            val fpsInterval = 1000L / frameRate
            var count = 0
            var fristTime = SystemClock.elapsedRealtime()
            while (isActive) {
                    val start = SystemClock.elapsedRealtime()
                    Utils.bitmapToMat(srcBitmap, mat)
                    if (RecordPauseLiveData.get().value != true)
                        videoWriter.write(mat)
                    val cha = SystemClock.elapsedRealtime() - start
                    LogPlus.i("bitmap转Mat花费===>$cha")
                    if (fpsInterval - cha > 4) {
                        delay(fpsInterval - cha - 4)
                    }
                    count++
                    if (count % frameRate == 0) {
                        LogPlus.w("${frameRate}帧花费时间==>${SystemClock.elapsedRealtime() - fristTime}")
                        fristTime = SystemClock.elapsedRealtime()
                    }
            }
            videoWriter.release()
            mat.release()
            // srcBitmap.recycle()
            LogPlus.e("关闭AVI录像了")
        }
    }


    private var lastInfo = -1L
    private var difValue = 0L
    private var currTime = System.currentTimeMillis() * 1000L


    private fun drainEncoder(endOfStream: Boolean, bufferInfo: MediaCodec.BufferInfo) {
        var buffers: Array<ByteBuffer?>? = null
        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP) {
            buffers = mediaCodec!!.outputBuffers
        }
        if (endOfStream) {
            try {
                mediaCodec.signalEndOfInputStream()
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
        while (true) {
            val encoderStatus = mediaCodec!!.dequeueOutputBuffer(bufferInfo, defaultTimeOutUs)
            if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
                if (!endOfStream) {
                    break
                }
            } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                if (mMuxerStarted) {
                    error { "format changed twice" }
                }
                val mediaFormat = mediaCodec.outputFormat
                mTrackIndex = mediaMuxer!!.addTrack(mediaFormat)
                mediaMuxer!!.start()
                mMuxerStarted = true
            } else if (encoderStatus < 0) {
                Log.d(
                    "YapVideoEncoder",
                    "unexpected result from encoder.dequeueOutputBuffer: $encoderStatus"
                )
            } else {
                val outputBuffer = (if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP) {
                    buffers!![encoderStatus]
                } else {
                    mediaCodec.getOutputBuffer(encoderStatus)
                }) ?: error { "encoderOutputBuffer $encoderStatus was null" }
                if (bufferInfo.flags and MediaCodec.BUFFER_FLAG_CODEC_CONFIG != 0) {
                    bufferInfo.size = 0
                }
                writeData(bufferInfo, outputBuffer)
                mediaCodec.releaseOutputBuffer(encoderStatus, false)
                if (bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0) {
                    if (!endOfStream) {
                        Log.d("YapVideoEncoder", "reached end of stream unexpectedly")
                    } else {
                        Log.d("YapVideoEncoder", "end of stream reached")
                    }
                    break
                }
            }
        }
    }

    private fun writeData(bufferInfo: MediaCodec.BufferInfo, outputBuffer: ByteBuffer) {
        if (bufferInfo.size != 0) {
            if (!mMuxerStarted) {
                Log.d("YapVideoEncoder", "error:muxer hasn't started")
            }
            outputBuffer.position(bufferInfo.offset)
            outputBuffer.limit(bufferInfo.offset + bufferInfo.size)

            bufferInfo.presentationTimeUs -= difValue
            val fpsCha: Long = bufferInfo.presentationTimeUs - currTime
            if (fpsCha > 1000 * 1000) {
                difValue += fpsCha - 20 * 1000
                bufferInfo.presentationTimeUs -= fpsCha - 20 * 1000
                LogPlus.e("数据很久没来了==>$fpsCha")
            }
            currTime = bufferInfo.presentationTimeUs
            LogPlus.i("当前的实际时间===>presentation:" + bufferInfo.presentationTimeUs)
            if (pause.value == true) {
                if (lastInfo == -1L) {
                    lastInfo = bufferInfo.presentationTimeUs
                    LogPlus.w("開始暂停--->" + lastInfo / 1000)
                }
                return
            }

            //1649902036343000
            if (lastInfo != -1L) {
                //差值叠加
                val cha: Long = bufferInfo.presentationTimeUs - lastInfo
                LogPlus.e("暂停后时间-->" + bufferInfo.presentationTimeUs + "-暂停时间-->" + lastInfo)
                difValue += cha
                lastInfo = -1L
                bufferInfo.presentationTimeUs -= cha
                LogPlus.w("暂停时间为===>" + difValue / 1000 + "--本次暂停==>" + cha / 1000)
            }
            try {
                mediaMuxer!!.writeSampleData(mTrackIndex, outputBuffer, bufferInfo)
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }

    }

    private fun getNV12(inputWidth: Int, inputHeight: Int, scaled: Bitmap?): ByteArray {
        val argb = IntArray(inputWidth * inputHeight)
        scaled!!.getPixels(argb, 0, inputWidth, 0, 0, inputWidth, inputHeight)
        val yuv = ByteArray(inputWidth * inputHeight * 3 / 2)
        encodeYUV420SP(yuv, argb, inputWidth, inputHeight)
        return yuv
    }

    private fun encodeYUV420SP(yuv420sp: ByteArray, argb: IntArray, width: Int, height: Int) {
        val frameSize = width * height
        var yIndex = 0
        var uvIndex = frameSize
        var index = 0
        for (j in 0 until height) {
            for (i in 0 until width) {
                // val a = argb[index] and -0x1000000 shr 24
                val r = argb[index] and 0xff0000 shr 16
                val g = argb[index] and 0xff00 shr 8
                val b = argb[index] and 0xff shr 0
                val y = (66 * r + 129 * g + 25 * b + 128 shr 8) + 16
                val u = (112 * r - 94 * g - 18 * b + 128 shr 8) + 128
                val v = (-38 * r - 74 * g + 112 * b + 128 shr 8) + 128
                yuv420sp[yIndex++] = (if (y < 0) 0 else if (y > 255) 255 else y).toByte()
                if (j % 2 == 0 && index % 2 == 0) {
                    yuv420sp[uvIndex++] = (if (v < 0) 0 else if (v > 255) 255 else v).toByte()
                    yuv420sp[uvIndex++] = (if (u < 0) 0 else if (u > 255) 255 else u).toByte()
                }
                index++
            }
        }
    }

    fun stopRecord(): String? = if (rocrodJob?.isActive == true) {
        rocrodJob?.cancel()
        out.absolutePath
    } else null

}